import Foundation
import CommonCrypto
import LRObjcTools
@objc public enum FYNetworkApi: Int {
    case sendVerifySms
    case registerOrLogin
    case logout
    case authClose
    case config
    case version
    case setting
    case userInfo
    case home
    case problemFeedback
    case suphome
    case upload
    case ifsc
    case kycStatus
    case kycInit
    case commit
    case itemCommit
    case livenessLicense
    case livenessDetection
    case livenessSilent
    case productList
    case productTerm
    case submit
    case userSubmit
    case orderList
    case orderDetail
    case withdrawDetail
    case readyCheck
    case appyWithdrawal
    case repayExtension
    case repayConnect
    case device
    case registerDevice
    case contact
    case contactBackup
    case bankCardlist
    case modifyBank
    case record
    var path: String {
        switch self {
        case .sendVerifySms:
            return "/app/v3/sms/sendVerifySms"
        case .registerOrLogin:
            return "/app/v3/auth/registerOrLogin"
        case .logout:
            return "/app/v3/auth/logout"
        case .authClose:
            return "/app/v3/auth/close"
        case .config:
            return "/app/v3/app/config"
        case .version:
            return "/app/v3/app/version"
        case .setting:
            return "/app/v3/app/settings"
        case .userInfo:
            return "/app/v3/user/info"
        case .home:
            return "/app/v3/user/home"
        case .problemFeedback:
            return "/app/v3/user/problemFeedback"
        case .suphome:
            return "/app/v3/user/suphome"
        case .upload:
            return "/app/v3/sys/upload"
        case .ifsc:
            return "/app/v3/sys/ifsc"
        case .kycStatus:
            return "/app/v3/kyc/status"
        case .kycInit:
            return "/app/v3/kyc/init"
        case .commit:
            return "/app/v3/kyc/commit"
        case .itemCommit:
            return "/app/v3/kyc/item/commit"
        case .livenessLicense:
            return "/app/v3/kyc/liveness/advance/license"
        case .livenessDetection:
            return "/app/v3/kyc/liveness/advance/detection"
        case .livenessSilent:
            return "/app/v3/kyc/liveness/silent"
        case .productList:
            return "/app/v3/product/list"
        case .productTerm:
            return "/app/v3/product/term"
        case .submit:
            return "/app/v3/order/submit"
        case .orderList:
            return "/app/v3/order/list"
        case .userSubmit:
            return "/app/v3/order/userSubmit"
        case .orderDetail:
            return "/app/v3/order/detail"
        case .withdrawDetail:
            return "/app/v3/order/withdrawn/detail"
        case .readyCheck:
            return "/app/v3/order/ready"
        case .appyWithdrawal:
            return "/app/v3/order/apply/withdrawal"
        case .repayExtension:
            return "/app/v3/order/repay/extension"
        case .repayConnect:
            return "/app/v3/order/repay/connect"
        case .device:
            return "/app/v3/mobile/device"
        case .registerDevice:
            return "/app/v3/mobile/registerDevice"
        case .contact:
            return "/app/v3/mobile/contact"
        case .contactBackup:
            return "/app/v3/mobile/contactBackup"
        case .bankCardlist:
            return "/app/v3/bankcard/list"
        case .modifyBank:
            return "/app/v3/bankcard/modify"
        case .record:
            return "/app/v3/bury/record"
        }
    }
    var needSign: [String] {
        switch self {
        case .sendVerifySms:
            return ["mobile", "verifyType"]
        case .registerOrLogin:
            return ["mobile", "verifyCode", "longitude", "latitude", "imei", "serialNo"]
        case .kycInit:
            return ["kycId"]
        case .commit:
            return ["kycId"]
        case .itemCommit:
            return ["kycId", "itemCode", "itemValueType", "itemValue"]
        case .livenessDetection:
            return ["livenessId"]
        case .livenessSilent:
            return ["liveness"]
        case .productTerm:
            return ["productId"]
        case .submit:
            return ["productId", "loanAmount", "bankCardBindId", "longitude", "latitude", "imei", "serialNo"]
        case .userSubmit:
            return ["productId", "loanAmount", "bankCardBindId", "longitude", "latitude", "imei", "serialNo"]
        case .orderDetail:
            return ["orderId"]
        case .withdrawDetail:
            return ["orderId"]
        case .readyCheck:
            return ["orderId"]
        case .appyWithdrawal:
            return ["orderId"]
        case .repayExtension:
            return ["orderId"]
        case .repayConnect:
            return ["orderId"]
        case .device:
            return ["orderId"]
        case .contact:
            return ["orderId"]
        case .modifyBank:
            return ["accountType", "bankCode", "accountNo"]
        case .record:
            return ["eventCode", "eventContent", "remark1", "remark2", "remark3"]
        default: return []
        }
    }
    var showLoading: Bool {
        switch self {
        case .config, .device, .registerDevice, .record, .userInfo, .productList, .version, .contact, .orderList, .suphome:
            return false
        default: return true
        }
    }
    var isUpload: Bool {
        switch self {
        case .upload: return true
        default: return false
        }
    }
}
public let FYNetworkUploadDataKey: String = "FYNetworkUploadDataKey"
public let FYNetworkUploadFileTypeKey: String = "FYNetworkUploadFileTypeKey"
@objcMembers open
class FYNetwork {
    private static let headers: [HTTPHeader] = [
        HTTPHeader(name: "User-Agent", value: FYNetwork.getUA()),
        HTTPHeader(name: "Content-Type", value: "application/json")
    ]
    private static let uploadHeaders: [HTTPHeader] = [
        HTTPHeader(name: "User-Agent", value: FYNetwork.getUA()),
        HTTPHeader(name: "Content-Type", value: "multipart/form-data")
    ]
    public static func request<T>(api: FYNetworkApi, forceHide: Bool = false, parameters: Parameters? = nil, completion: ((T)->Void)?) where T: FYBaseModel {
        if api.showLoading, forceHide == false { SVProgressHUD.show() }
        AF.sessionConfiguration.timeoutIntervalForRequest = 200
        let currentUrl: String = FYCustomTools.shared.applictionBaseUrl + api.path
        let param = FYNetwork.fixParameters(parameters, extSign: api.needSign)
        let completionHandler: ((AFDataResponse<Data?>) -> Void) = { response in
            if api.showLoading { SVProgressHUD.dismiss() }
            if response.error != nil {
                LRHostManager.checkHost(currentUrl) { reachable in
                    if reachable {
                        FYNetwork.request(api: api, forceHide: true, parameters: parameters, completion: completion)
                    } else {
                        SVProgressHUD.showError(withStatus: "Internet connection failed,please try again later")
                    }
                }
                return
            }
            LRHostManager.hostSuccess()
            guard let value = response.data else { return }
            #if DEBUG
            print("\n url: \(currentUrl) \n param: \(param) \n result: \(String(data: value, encoding: .utf8) ?? "")", terminator: "\n")
            #endif
            guard let model = value.kj.model(T.self) else {
                if api.showLoading {
                    SVProgressHUD.showError(withStatus: "Internet connection failed,please try again later")
                }
                return
            }
            let inValid: [FYResultCode] = [.tokenExpire, .userInvalid, .tokenInvalid]
            if inValid.contains(where: { $0 == model.resultCode }) { FYCustomTools.shared.delegate?.fy_applicationNeedReLogin?(); return }
            if model.resultCode == .needUpdate { FYCustomTools.shared.delegate?.fy_applicationNeedUpgrade?(); return }
            if model.success() {
                completion?(model)
            } else {
                if api.showLoading {
                    if model.resultCode == .timeOut {
                        SVProgressHUD.showError(withStatus: "Internet connection failed,please try again later")
                    } else if model.resultMsg.isValid() {
                        SVProgressHUD.showError(withStatus: model.resultMsg)
                    }
                }
            }
        }
        if api.isUpload {
            guard let imageData: Data = parameters?[FYNetworkUploadDataKey] as? Data else { return }
            var fileType: String = "image"
            if let type: String = parameters?[FYNetworkUploadFileTypeKey] as? String {
                fileType = type
            }
            let uuid: String = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased()
            let fileName: String = "\(FYCustomTools.shared.applictionID)_\(uuid)_\(fileType)_\(param["clientTime"] ?? "").jpg"
            AF.upload(multipartFormData: { formData in
                formData.append(imageData, withName: "file", fileName: fileName, mimeType: "image/*")
                for (key, value) in param {
                    if let data_ = "\(value)".data(using: String.Encoding.utf8) {
                        formData.append(data_, withName: key)
                    }
                }
            }, to: currentUrl, method: .post, headers: HTTPHeaders(FYNetwork.uploadHeaders)).response(queue: .main, completionHandler: completionHandler)
        } else {
            AF.request(currentUrl, method: .post, parameters: param, encoding: JSONEncoding.default, headers: HTTPHeaders(FYNetwork.headers)).response(queue: .main, completionHandler: completionHandler)
        }
    }
    private class func fixParameters(_ parameters: Parameters?, extSign: [String]) -> Parameters {
        var parameter: Parameters = [:]
        parameter["appId"] = FYCustomTools.shared.applictionID
        parameter["deviceId"] = FYDevice.getUUID()
        parameter["os"] = 2
        parameter["channel"] = "app_store"
        parameter["version"] = "2.0"
        let clientTime = (Int)(Date().timeIntervalSince1970 * 1000)
        parameter["clientTime"] = clientTime
        let nonce = FYNetwork.generateNonce()
        parameter["nonce"] = nonce
        var signPara = parameter
        if extSign.isEmpty == false, let parameters = parameters {
            extSign.forEach { key in
                signPara[key] = parameters[key]
            }
        }
        let sign = self.createSignString(parameters: signPara)
        parameter["sign"] = sign
        parameter["clientLanguage"] = "en"
        parameter["clientVersion"] = SystemServices.shared().applicationVersion ?? ""
        parameter["userId"] = FYCustomTools.shared.applictionUserId
        parameter["token"] = FYCustomTools.shared.applictionToken
        parameter["data"] = parameters ?? [:]
        return parameter
    }
    private class func createSignString(parameters: Parameters) -> String {
        var sign: String = ""
        guard var keys: [String] = (parameters as NSDictionary).allKeys as? [String] else { return sign }
        keys.sort(by: { $0 < $1 })
        var string: String = ""
        for index in 0 ..< keys.count {
            let key = keys[index]
            if let value = parameters[key] {
                string.append("\(key)=\(value)&")
            } else {
                string.append("\(key)=&")
            }
        }
        string.removeLast()
        sign = (string.md5 + FYCustomTools.shared.applictionSalt).md5
        return sign
    }
    private class func generateNonce(_ length: Int = 16) -> String {
        let character: NSString = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        var nonce: String = ""
        for _ in 0 ..< length {
            let index: Int = (Int)(arc4random_uniform(UInt32(character.length)));
            nonce = nonce.appendingFormat("%C", character.character(at: index))
        }
        return nonce
    }
    @objc public class func getUA() -> String {
        let systemVersion = UIDevice.current.systemName + " " + UIDevice.current.systemVersion;
        let descriptionDevice = FYDevice.getTypeFormatted()
        let userAgent = "\(FYCustomTools.shared.applictionID)\(SystemServices.shared().applicationVersion ?? "")(Apple;Mobile;\(descriptionDevice);\(systemVersion);)"
        return userAgent;
    }
}
extension String {
    var md5: String {
        guard let data = self.data(using: .utf8) else { return "" }
        var digest: [UInt8] = Array(repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        _ = data.withUnsafeBytes { bytes in
            CC_MD5(bytes.baseAddress, CC_LONG(data.count), &digest)
        }
        return digest.map({ s in
            var str = String(s, radix: 16, uppercase: true)
            if str.count == 1 {
                str = "0".appending(str)
            }
            return str
        }).joined()
    }
}
